<?php

/**
 * This file is part of Webman AI.
 *
 * @author    walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link      http://www.workerman.net/
 */

namespace plugin\ai\app\handler\driver;

use plugin\ai\app\service\Common;
use Throwable;
use Webman\Openai\Chat;
use Workerman\Http\Client;
use Workerman\Http\Response;
use Intervention\Image\ImageManagerStatic as Image;

class Gpt extends Chat
{

    /**
     * @param array $data
     * @param array $options
     * @return void
     * @throws Throwable
     */
    public function completions(array $data, array $options)
    {
        $data = $this->formatData($data);
        if (isset($options['complete'])) {
            $options['complete'] = function ($result, Response $response) use ($data, $options) {
                if (isset($result['error'])) {
                    return $options['complete']($result, $response);
                }
                if (!empty($result['choices'][0]['message']['tool_calls'][0])) {
                    $options['complete']($result['choices'][0]['message']['tool_calls'][0], $response);
                } else {
                    $options['complete']($result['choices'][0]['message']['content'], $response);
                }
            };
        }
        if (isset($options['stream'])) {
            $options['stream'] = function ($data) use ($options) {
                $data = array_merge(['content' => ''], $data);
                unset($data['model']);
                $data['content'] = $data['choices'][0]['delta']['content'] ?? '';
                $options['stream']($data);
            };
        }
        $headers = $this->getHeaders($options);
        if (isset($options['stream'])) {
            $data['stream'] = true;
        }
        $stream = !empty($data['stream']) && isset($options['stream']);
        $options = $this->formatOptions($options);
        $requestOptions = [
            'method' => 'POST',
            'data' => json_encode($data, JSON_UNESCAPED_UNICODE),
            'headers' => $headers,
            'progress' => function ($buffer) use ($options) {
                static $tmp = '';
                $tmp .= $buffer;
                if ($tmp === '' || $tmp[strlen($tmp) - 1] !== "\n") {
                    return null;
                }
                preg_match_all('/data: *?(\{.+?\})[ \r]*?\n/', $tmp, $matches);
                $tmp = '';
                foreach ($matches[1]?:[] as $match) {
                    if ($json = json_decode($match, true)) {
                        $options['stream']($json);
                    }
                }
            },
            'success' => function (Response $response) use ($options) {
                $result = static::formatResponse((string)$response->getBody());
                $options['complete']($result, $response);
            },
            'error' => function ($exception) use ($options) {
                $options['complete']([
                    'error' => [
                        'code' => 'exception',
                        'message' => $exception->getMessage(),
                        'detail' => (string) $exception
                    ],
                ], new Response(0));
            }
        ];
        if (!$stream) {
            unset($requestOptions['progress']);
        }
        $model = $data['model'] ?? '';
        $url = $this->api;
        if (!$path = parse_url($this->api, PHP_URL_PATH)) {
            $url = $this->api . ($this->isAzure ? "/openai/deployments/$model/chat/completions?api-version=$this->azureApiVersion" : "/v1/chat/completions");
        } else if ($path[strlen($path) - 1] === '/') {
            $url = $this->api . 'chat/completions';
        }
        $http = new Client(['timeout' => 600]);
        $http->request($url, $requestOptions);
    }

    /**
     * @param $data
     * @return array
     */
    protected function formatData($data): array
    {
        $messages = $data['messages'];
        $model = $data['model'];
        if (strpos($model, 'gpt') === false && strpos($model, 'glm') === false) {
            return $data;
        }
        $isGptVision = strpos($model, 'gpt-4-vision') !== false || strpos($model, 'gpt-4-turbo') !== false || strpos($model, 'gpt-4o') !== false || strpos($model, 'glm-4v') !== false;
        foreach ($messages as $key => $message) {
            if (is_array($message['content'])) {
                if ((!$isGptVision || !isset($message['content'][0]['type']))) {
                    $messages[$key]['content'] = json_encode($message['content'], JSON_UNESCAPED_UNICODE);
                } else {
                    foreach ($message['content'] as $k => $v) {
                        if ($v['type'] === 'image_url') {
                            $url = $v['image_url']['url'];
                            if ($file = Common::urlToFile($url)) {
                                try {
                                    $image = Image::make($file);
                                    $mime = $image->mime();
                                    if($mime) {
                                        $messages[$key]['content'][$k]['image_url']['url'] = "data:$mime;base64," . base64_encode(file_get_contents($file));
                                    }
                                } catch (Throwable $e) {
                                    /* do nothing */
                                }
                            }
                        }
                    }
                }
            } elseif ($message['content'] === '') {
                // For kimi
                $messages[$key]['content'] = ' ';
            }

        }
        $data['messages'] = $messages;
        if ($model === 'gpt-4-vision-preview' && !isset($data['max_tokens'])) {
            $data['max_tokens'] = 2000;
        }
        return $data;
    }

}